﻿using System;
using System.Globalization;
using System.Runtime.InteropServices;
using System.Threading;
using System.Xml;
using System.Xml.Linq;
using RoMBot.MemoryLib;

namespace RoMBot.Classes
{
    public sealed class Skill
    {
        volatile private int used;
        // <skill name="MAGE_FIREBALL" aslevel="4" skilltab="2" skillnum="1" mana="30" manainc="3" range="225" cooldown="6" priority="65" type="damage" target="enemy" />

        public Skill()
        {
            this.HotKey = Key.VK_NONE;
            this.Modifier = Key.VK_NONE;
            this.Used = 0;
            this.MaxUse = -1;
            this.RebuffCut = 0;
            this.AutoUse = true;
            this.MaxHpPer = 100;
            this.MaxManaPer = 100;
            this.TargetMaxHpPer = 100;
            this.TargetMaxHp = 999999999;
            this.InBattle = null;
        }

        public Skill( XElement d )
            : this( d.Attribute( "name" ).Value )
        {
            UpdateFromElement( d );
        }

        public Skill( string id )
            : this()
        {
            if ( Bot.Instancia.Skills.ContainsKey( id ) )
            {
                var skill = Bot.Instancia.Skills[ id ];
                this.ID = id;
                this.Id = skill.Id;
                this.Name = skill.Name;
                this.NeedLevel = skill.NeedLevel;
                this.SkillTab = skill.SkillTab;
                this.SkillNum = skill.SkillNum;
                this.Mana = skill.Mana;
                this.ManaInc = skill.ManaInc;
                this.Range = skill.Range;
                this.MinRange = skill.MinRange;
                this.Cooldown = skill.Cooldown;
                this.CastTime = skill.CastTime;
                this.Priority = skill.Priority;
                this.CastTime = skill.CastTime;
                this.Type = skill.Type;
                this.TargetType = skill.TargetType;
                this.Locale = skill.Locale;
                this.Level = skill.Level;
                this.BuffName = skill.BuffName;
                this.ReqBuffName = skill.ReqBuffName;
                this.ReqBuffCount = skill.ReqBuffCount;
                this.ReqBuffTarget = skill.ReqBuffTarget;
                this.AoECenter = skill.AoECenter;
                this.AoERange = skill.AoERange;
                //this.InBattle = skill.InBattle;
                this.Cooldown = skill.Cooldown;
                this.GlobalCooldown = skill.GlobalCooldown;
                //this.MaxHpPer = skill.MaxHpPer;
                this.RebuffCut = skill.RebuffCut;
                this.Nature = skill.Nature;
            }
        }

        public Skill( int address )
        {
            this.Address = address;
            if ( this.Address != 0 )
            {
                this.Update();
            }
        }

        public void UpdateFromNode( XmlNode node )
        {
            UpdateFromElement( XElement.Parse( node.OuterXml ) );
        }

        private void UpdateFromElement( XElement d )
        {
            HotKey = d.Attribute( "hotkey" ) != null ? Enum.IsDefined( typeof( Key ), d.Attribute( "hotkey" ).Value ) ? ( Key ) Enum.Parse( typeof( Key ), d.Attribute( "hotkey" ).Value ) : Key.VK_NONE : Key.VK_NONE;
            Modifier = d.Attribute( "modifier" ) != null ? Enum.IsDefined( typeof( Key ), d.Attribute( "modifier" ).Value ) ? ( Key ) Enum.Parse( typeof( Key ), d.Attribute( "modifier" ).Value ) : Key.VK_NONE : Key.VK_NONE;
            MaxUse = d.Attribute( "maxuse" ) != null ? Convert.ToInt32( d.Attribute( "maxuse" ).Value ) : -1;
            RebuffCut = d.Attribute( "rebuffcut" ) != null ? Convert.ToSingle( d.Attribute( "rebuffcut" ).Value ) : 0f;
            MaxHpPer = d.Attribute( "hpper" ) != null && !string.IsNullOrWhiteSpace( d.Attribute( "hpper" ).Value ) ? Convert.ToInt32( d.Attribute( "hpper" ).Value.Replace( "!", "" ) ) : 100;
            TargetMaxHpPer = d.Attribute( "targethpper" ) != null ? Convert.ToInt32( d.Attribute( "targethpper" ).Value.Replace( "!", "" ) ) : 100;
            MaxManaPer = d.Attribute( "manaper" ) != null && !string.IsNullOrWhiteSpace( d.Attribute( "manaper" ).Value ) ? Convert.ToInt32( d.Attribute( "manaper" ).Value.Replace( "!", "" ) ) : 100;
            ReqBuffCount = d.Attribute( "reqbuffcount" ) != null ? Convert.ToInt32( d.Attribute( "reqbuffcount" ).Value ) : 0;
            Priority = d.Attribute( "priority" ) != null ? Convert.ToInt32( d.Attribute( "priority" ).Value ) : 0;
            InBattle = d.Attribute( "inBattle" ) != null ? Convert.ToBoolean( d.Attribute( "inBattle" ).Value ) : ( bool? ) null;
            PullOnly = d.Attribute( "pullonly" ) != null && Convert.ToBoolean( !string.IsNullOrWhiteSpace( d.Attribute( "pullonly" ).Value ) ? d.Attribute( "pullonly" ).Value : "false" );
            if ( MaxHpPer != 100 )
            {
                if ( d.Attribute( "hpper" ).Value.Contains( "!" ) )
                {
                    MaxHpPer = -MaxHpPer;
                }
            }

            if ( MaxManaPer != 100 )
            {
                if ( d.Attribute( "manaper" ).Value.Contains( "!" ) )
                {
                    MaxManaPer = -MaxManaPer;
                }
            }

            if ( TargetMaxHpPer != 100 )
            {
                if ( d.Attribute( "targethpper" ).Value.Contains( "!" ) )
                {
                    TargetMaxHpPer = -TargetMaxHpPer;
                }
            }
            if ( d.Attribute( "autouse" ) != null && !string.IsNullOrWhiteSpace( d.Attribute( "autouse" ).Value ) )
            {
                this.AutoUse = Convert.ToBoolean( d.Attribute( "autouse" ).Value.ToLowerInvariant() );
            }
            //Update();
        }

        public void Update( TabSkill tabSkill = null )
        {
            var skill = tabSkill ?? Memory.ReadObject<TabSkill>( this.Address );
            if ( skill == null )
            {
                return;
            }
            var proc = Bot.Instancia.ProcId;
            this.Id = skill.Id; //Memory.ReadRepeat<int>( proc, this.Address );
            this.Name = MemoryTable.GetIdName( this.Id );
            this.TPToLevel = skill.skillTPToLevel;// Memory.ReadRepeat<int>( proc, this.Address + Addresses.skillTPToLevel_offset );
            this.Level = skill.skillLevel;// Memory.ReadRepeat<int>( proc, this.Address + Addresses.skillLevel_offset );
            this.Aslevel = skill.skillAsLevel;// Memory.ReadRepeat<int>( proc, this.Address + Addresses.skillAsLevel_offset );
            this.BaseItemAddress = MemoryTable.GetItemAddress( this.Id );
            this.Cooldown = Memory.ReadRepeat<int>( proc, this.BaseItemAddress + Addresses.skillCooldown_offset );

            this.HotKey = Key.MACRO;

            for ( var count = 0; count < 2; count++ )
            {
                this.PowerType = ( PowerType ) Memory.ReadRepeat<int>( proc, this.BaseItemAddress + ( 8 * count ) + Addresses.skillUsesBase_offset );
                if ( this.PowerType == PowerType.None )
                    break;

                var usesnum = Memory.ReadRepeat<int>( proc, this.BaseItemAddress + ( 8 * count ) + Addresses.skillUsesBase_offset + 4 );

                switch ( this.PowerType )
                {
                    case PowerType.Mana:
                        if ( this.Level > 49 )
                        {
                            this.Mana = ( int ) ( usesnum * ( 5.8 + ( this.Level - 49 ) * 0.2 ) );
                        }
                        else if ( this.Level > 1 )
                        {
                            this.Mana = ( int ) ( usesnum * ( 1 + ( this.Level - 1 ) * 0.1 ) );
                        }
                        else
                        {
                            this.Mana = usesnum;
                        }
                        break;
                    case PowerType.Rage:
                        this.Rage = usesnum;
                        break;
                    case PowerType.Focus:
                        this.Focus = usesnum;
                        break;
                    case PowerType.Energy:
                        this.Energy = usesnum;
                        break;
                    case PowerType.MpPercent:
                        break;
                    case PowerType.Item:
                        this.Consumable = "item";
                        this.ConsumableNumber = usesnum;
                        break;
                    case PowerType.Proyectile:
                        this.Consumable = "proyectile";
                        this.ConsumableNumber = usesnum;
                        break;
                    case PowerType.Arrow:
                        this.Consumable = "arrow";
                        this.ConsumableNumber = usesnum;
                        break;
                    case PowerType.Psi:
                        this.Psi = usesnum;
                        break;
                }
            }
        }


        /// <summary>
        /// Usa el skill
        /// </summary>
        /// <returns>true si pudo usarse, false de lo contrario</returns>
        public bool Use()
        {
            //var estimatedMana = 0m;
            //estimatedMana = this.MinManaPer > 0 ? Math.Ceiling( ( decimal ) ( ( this.MinManaPer / 100 ) * Bot.Player.MaxMana ) ) : Math.Ceiling( ( decimal ) ( this.Mana + ( this.Level - 1 ) * this.ManaInc ) );

            //var target = Bot.Player.Target;

            if ( this.HotKey == Key.VK_NONE )
            {
                var str = string.Format( "Bad skill hotkey name: {0}.\n", this.Name );
                Logger.Log( str, LogType.Error );
                return false;
            }

            Bot.Player.Profile.OnBeforeSkillCast( this );

            // count use of skill per fight
            this.used++;
            //this.Used++;	
            //Bot.Printf( Color.HotPink, "Skill: {0} used {1} times in current fight. ({2})", Name, Used, GetHashCode() );

            this.LastCastTime = DateTime.Now;
            this.LastCastTime += TimeSpan.FromMilliseconds( this.CastTime * 1000 * 3345 );
            if ( this.CastTime > 0 )
            {
                Bot.Player.LastSkillCastTime = this.CastTime;
                Bot.Player.LastSkillType = this.Type;
            }

            while ( ( DateTime.Now - Bot.Instancia.LastSkillKeypressTime ).TotalMilliseconds < ( Bot.Player.Profile.GetOptionValue<int>( "SKILL_GLOBALCOOLDOWN" ) - Bot.Player.Profile.GetOptionValue<int>( "SKILL_USE_PRIOR" ) ) )
            {
                Thread.CurrentThread.Join( 10 );
            }

            Bot.Instancia.LastSkillKeypressTime = DateTime.Now;		// remember time to check time-lag between casts

            if ( this.HotKey == Key.MACRO )
            {

                // hopefully skillnames in enus and eneu are the same
                if ( Bot.Instancia.Locale == this.Locale )
                {		// is local skill name available?
                    Helpers.RoMScript( "CastSpellByName('" + this.Name + "');" );
                }
                else if ( this.SkillTab > 0 && this.SkillNum > 0 )
                {
                    Helpers.RoMScript( "UseSkill(" + this.SkillTab + "," + this.SkillNum + ");" );
                }
                else
                {
                    //cprintf(cli.yellow, "No local skillname in skills_local.xml. Please maintenance the file and send it to the developers.\n", this.Name);
                    Bot.Instancia.Log += string.Format( "No local skillname {0}\n", this.Name );
                }
            }
            else
            {
                // use the normal hotkeys
                Keyboard.KeyPress( this.HotKey, this.Modifier );
            }

            if ( this.Toggleable )
            {
                this.Toggled = true;
            }
            Bot.Player.LastSkill = this;
            Bot.Player.Profile.OnSkillCast( this );
            if ( this.Type == SkillType.Buff )
            {

            }
            return true;
        }

        private void DebugSkillUse( string reason, params object[] args )
        {
            if ( Bot.DebugSkills && !string.IsNullOrEmpty( reason ) )
            {
                Bot.Debug( string.Format( "{0} can't be used {1}.", Name, string.Format( reason, args ) ) );
            }
        }

        public bool CanUse( bool onlyFriendly = false, Pawn target = null )
        {
            if ( !this.Available )
            {
                return false;
            }

            if ( this.HotKey == Key.VK_NONE )
                return false;

            //Is player level more than or equal to aslevel
            if ( Player.Level < this.Aslevel )
            {
                DebugSkillUse( "Player level too low" );
                //debug_skilluse("ASLEVEL", player.Level, self.aslevel);
                return false;
            }

            if ( this.MaxUse > 0 && this.Used >= this.MaxUse )
            {
                DebugSkillUse( "Max uses exceeded" );
                return false;
            }

            if ( target == null )
            {
                target = new Pawn( Player.TargetPtr );
            }

            this.Target = target;

            //only friendly skill types ?
            if ( onlyFriendly && ( ( this.Type == SkillType.Damage || this.Type == SkillType.DoT ) || ( this.Type == SkillType.Buff && this.TargetType == SkillTargetType.Enemy ) ) )
            {
                DebugSkillUse( "Only friendly specified and this is type: {0}", this.Type );
                return false;
            }

            var prior = Helpers.SkillUsePrior;
            // Still cooling down...
            if ( this.Cooldown > 0 )
            {
                var rem = RemainingCooldown;
                //var usePrior = Bot.Player.Profile.GetOptionValue<int>( "SKILL_USE_PRIOR" );
                if ( rem >= 1 )
                {
                    DebugSkillUse( "Is on cooldown, remaining: {0}", rem );
                    return false;
                }
            }

            if ( Math.Abs( Player.MaxHP - double.Epsilon ) <= double.Epsilon )
                Player.MaxHP = 1; //prevent division by zero
            if ( Math.Abs( target.MaxHP - double.Epsilon ) <= double.Epsilon )
                target.MaxHP = 1;

            if ( target.Type != ObjectType.Player ) // no target or enemy target, heal self
            {
                if ( ( this.MaxHpPer < 0 ? -1 : 1 ) * ( Player.HP / Player.MaxHP * 100 ) > this.MaxHpPer )
                {
                    DebugSkillUse( "MaxHpPer player:{0}%, MaxHpPer:{1}", Player.HP / Player.MaxHP * 100, this.MaxHpPer );
                    return false;
                }
            }
            else
            {
                // === Heal friendly target, including self === //
                if ( ( this.MaxHpPer < 0 ? -1 : 1 ) * ( target.HP / target.MaxHP * 100 ) > this.MaxHpPer )
                {
                    DebugSkillUse( "MaxHpPer target:{0}%, MaxHpPer:{1}", target.HP / target.MaxHP * 100, this.MaxHpPer );
                    return false;
                }
            }

            //You are not below the maximum Mana Percent
            Player.UpdateMP();
            if ( ( this.MaxManaPer < 0 ? -1 : 1 ) * ( Player.Mana / Player.MaxMana * 100 ) > this.MaxManaPer )
            {
                DebugSkillUse( "MaxManaPer target:{0}%, MaxManaPer:{1}", ( Player.Mana / Player.MaxMana * 100 ), this.MaxManaPer );
                return false;
            }

            //// You are not above the minimum Mana Percent
            //if ( ( Player.Mana / Player.MaxMana * 100 ) < this.MinManaPer )
            //{
            //    DebugSkillUse( "MinManaPer target:{0}%, MaxManaPer:{1}", ( Player.Mana / Player.MaxMana * 100 ), this.MinManaPer );
            //    return false;
            //}

            // Not enough mana / rage / energy / focus / psi
            Player.UpdatePsi();
            if ( Player.Mana < this.Mana || Player.Rage < this.Rage || Player.Energy < this.Energy
                || Player.Focus < this.Focus || Player.Psi < this.Psi )
            {
                DebugSkillUse( "Not enough power." );
                return false;
            }

            // Needs Willpower state.Willpower Blade, Willpower Construct
            if ( this.Psi > 0 && ( !Player.HasBuff( "501571" ) ) && ( !Player.HasBuff( "501572" ) ) )
            {
                DebugSkillUse( "Needs Willpower state." );
                return false;
            }

            //Main buffs that require 6 psi but don't consume psi.
            if ( ( this.Id == 497955 || // Willpower Blade
               this.Id == 497956 ) && // Willpower Construct
               Player.Psi < 6 )
            {
                DebugSkillUse( "Needs 6 psi." );
                return false;
            }

            // This skill cannot be used in battle
            if ( ( !Player.Battling && Player.CastToTarget == 0 ) && ( this.InBattle ?? false ) )
            {
                DebugSkillUse( "This skill cannot be used in battle. Battling:{0}, Fighting:{1}", Player.Battling, Player.Fighting );
                return false;
            }

            // This skill can only be used in battle
            if ( ( Player.Battling || Player.CastToTarget > 0 ) && !( this.InBattle ?? true ) )
            {
                DebugSkillUse( "This skill can only be used in battle. Battling:{0}, Fighting:{1}", Player.Battling, Player.Fighting );
                return false;
            }

            //check if hp below our HP_LOW level
            if ( this.Type == SkillType.Heal || this.Type == SkillType.HoT )
            {
                var hpper = ( Player.HP / Player.MaxHP * 100 );
                if ( target.Type != ObjectType.Player || this.TargetType == SkillTargetType.Self )
                {
                    if ( this.MaxHpPer != 100 )
                    {
                        if ( hpper > this.MaxHpPer )
                        {
                            return false;
                        }
                    }
                    else
                    {
                        //Inherit from settings' HP_LOW
                        if ( hpper > Player.Profile.GetOptionValue<int>( "HP_LOW" ) )
                        {
                            //debug_skilluse("HPLOW", hpper, "greater as setting", settings.profile.options.HP_LOW );
                            return false;
                        }
                    }
                }
                else
                {
                    hpper = ( target.HP / target.MaxHP * 100 );
                    if ( this.MaxHpPer != 100 )
                    {
                        if ( hpper > this.MaxHpPer )
                        {
                            return false;
                        }
                    }
                    else
                    {
                        //Inherit from settings' HP_LOW
                        if ( hpper > Player.Profile.GetOptionValue<int>( "HP_LOW" ) )
                        {
                            //debug_skilluse("HPLOW", hpper, "greater as setting", settings.profile.options.HP_LOW );
                            return false;
                        }
                    }
                }
            }

            if ( this.TargetType == SkillTargetType.Enemy )
            {
                if ( target.Type != ObjectType.Monster || !target.Alive )
                {
                    return false;
                }
            }

            if ( this.Type == SkillType.DoT )
            {
                var hpper = ( target.HP / target.MaxHP ) * 100;
                if ( hpper < Player.Profile.GetOptionValue<int>( "DOT_PERCENT" ) )
                {
                    DebugSkillUse( "DoT Hp % {0} < {1} ", hpper, Player.Profile.GetOptionValue<int>( "DOT_PERCENT" ) );
                    //debug_skilluse("DOTHPPER", hpper, settings.profile.options.DOT_PERCENT);
                    return false;
                }
            }

            //only if target HP % is below given level
            if ( ( ( this.TargetMaxHpPer < 0 ? -1 : 1 ) * ( target.HP / target.MaxHP * 100 ) ) > this.TargetMaxHpPer )
            {
                DebugSkillUse( "Target hp %{0} is higher than {1} ", ( target.HP / target.MaxHP * 100 ), this.TargetMaxHpPer );
                return false;
            }

            // only if target HP points is below given level
            if ( ( ( this.TargetMaxHp < 0 ? -1 : 1 ) * target.HP ) > this.TargetMaxHp )
            {
                DebugSkillUse( "Target hp {0} is higher than {1} ", target.HP, this.TargetMaxHp );
                return false;
            }

            if ( Player.DistanceToTarget > this.Range && ( this.TargetType != SkillTargetType.Self && this.TargetType != SkillTargetType.None ) )
            {
                DebugSkillUse( "Distance to target: {0} is higher than Range:{1} ", Player.DistanceToTarget, this.Range );
                return false;
            }

            if ( Player.DistanceToTarget < this.MinRange && ( this.TargetType != SkillTargetType.Self && this.TargetType != SkillTargetType.None ) )
            {
                DebugSkillUse( "Distance to target: {0} is lower than MinRange:{1} ", Player.DistanceToTarget, this.MinRange );
                return false;
            }

            if ( this.PullOnly && ( !Player.RangedPull || Player.CastToTarget > 0 ) )
            {
                DebugSkillUse( "This skill is only for pulling" );
                return false;
            }

            if ( !string.IsNullOrEmpty( this.Consumable ) )
            {
                Player.Equipment.BagSlot[ 8 ].Update();
                var ammo = Player.Equipment.BagSlot[ 8 ];
                switch ( this.Consumable )
                {
                    case "arrow":
                        if ( ammo.ObjSubType != 5 || ammo.ItemCount < this.ConsumableNumber )
                        {
                            DebugSkillUse( "This skill needs arrows" );
                            return false;
                        }
                        break;
                    case "projectile":
                        if ( ammo.ObjSubType != 6 || ammo.ItemCount < this.ConsumableNumber )
                        {
                            DebugSkillUse( "This skill needs throwable" );
                            return false;
                        }
                        break;
                    case "item":
                        if ( Player.Inventory.GetItemCount( this.ConsumableNumber ) <= 0 )
                        {
                            DebugSkillUse( "This skill needs item" );
                            return false;
                        }
                        break;
                }
            }

            if ( this.Type == SkillType.Summon && Player.Class1 == PawnClass.Warden )
            {
                Player.UpdatePet();
                if ( Player.Pet != null && Player.Pet.Name != GameObject.UnknowName )
                {
                    DebugSkillUse( "Pet already out." );
                    Player.petWaitWatch.Restart();
                    return false;
                }
                if ( Player.petWaitWatch.Elapsed.TotalSeconds < 15 )
                {
                    return false;
                }
            }

            //check if 'this. has buff
            var lastCast = Player.LastSkill != null ? ( DateTime.Now - Player.LastSkill.LastCastTime ).TotalMilliseconds : 0;
            if (
                ( this.Type == SkillType.Buff || this.Type == SkillType.HoT ) &&
                !string.IsNullOrWhiteSpace( this.BuffName ) &&
                ( this.TargetType != SkillTargetType.Friendly && this.TargetType != SkillTargetType.Self ) )
            {
                if ( Player.LastSkill != null && !( Player.LastSkill.Id == this.Id && lastCast < 1000 ) )
                {
                    var buffitem = Player.GetBuff( this.BuffName );
                    // === check for -1 for buffs with no duration like rogue hide === //
                    if ( buffitem != null && ( ( buffitem.TimeLeft > ( this.RebuffCut + prior / 1000 ) ) || buffitem.TimeLeft < 0 ) )
                    {
                        DebugSkillUse( "Player already has buff {0}", buffitem.Name );
                        return false;
                    }
                }
                else if ( Player.LastSkill != null && ( Player.LastSkill.Id == this.Id && lastCast < 1000 ) )
                {
                    DebugSkillUse( "We casted that buff les than 1 second ago." );
                    return false;
                }

                if ( this.Toggled )
                {
                    this.Toggled = false;
                }
            }

            if ( this.Toggleable && this.Toggled )
            {
                DebugSkillUse( "Skill already toggled." );
                return false;
            }

            if ( ( this.Type == SkillType.Damage || this.Type == SkillType.DoT ) && !string.IsNullOrWhiteSpace( this.BuffName ) &&
              target != null && target.Type == ObjectType.Monster )
            {
                if ( target.HasBuff( this.BuffName ) )
                {
                    DebugSkillUse( "Target has the buff." );
                    return false;
                }
            }

            if ( ( this.Type == SkillType.Buff || this.Type == SkillType.HoT ) && !string.IsNullOrWhiteSpace( this.BuffName ) &&
                ( this.TargetType == SkillTargetType.Friendly || this.TargetType == SkillTargetType.Self ) )
            {
                if ( target != null && target.Type == ObjectType.Player )
                {
                    if ( target.HasBuff( this.BuffName ) )
                    {
                        DebugSkillUse( "Target has the buff." );
                        return false;
                    }
                }
                else if ( Player.HasBuff( this.BuffName ) )
                {
                    DebugSkillUse( "Player has the buff." );
                    return false;
                }
            }

            if ( !string.IsNullOrWhiteSpace( this.ReqBuffName ) )
            {
                var tiene = false;
                switch ( this.ReqBuffTarget )
                {
                    case RequiredTargetType.Player:
                        tiene = Player.HasBuff( this.ReqBuffName, this.ReqBuffCount );
                        break;
                    case RequiredTargetType.Target:
                        var req = this.ReqBuffName;
                        if ( this.ReqBuffCount > 0 )
                        {
                            int buffId;
                            if ( int.TryParse( this.ReqBuffName, out buffId ) )
                            {
                                req = ( buffId + this.ReqBuffCount - 1 ).ToString( CultureInfo.InvariantCulture );
                            }
                        }
                        tiene = target.HasBuff( req, this.ReqBuffCount );
                        break;
                }
                if ( !tiene )
                {
                    DebugSkillUse( "Doesn't have required buff." );
                    return false;
                }
            }

            if ( !string.IsNullOrWhiteSpace( this.NoBuffName ) )
            {
                var tiene = false;
                if ( this.NoBuffTarget == RequiredTargetType.Player )
                {
                    tiene = Player.HasBuff( this.NoBuffName, this.NoBuffCount );
                }
                else if ( target != null && this.NoBuffTarget == RequiredTargetType.Target )
                {
                    tiene = target.HasBuff( this.NoBuffName, this.NoBuffCount );
                }
                if ( tiene )
                {
                    DebugSkillUse( "Already have required buff." );
                    return false;
                }
            }

            Player.UpdateNature();
            if ( this.Nature > Player.Nature && !Player.HasBuff( "503817" ) ) //No need NP if has buff "Unity with Mother Earth"
            {
                DebugSkillUse( "Not enough Nature." );
                return false;
            }

            //Warden pet heal
            if ( this.Id == 493398 )
            {
                Player.Pet.UpdateName();
                if ( Player.Pet.Name == GameObject.UnknowName || ( Player.Pet.HP / Player.Pet.MaxHP * 100 ) > 70 )
                {
                    DebugSkillUse( "Pet HP is higher than 70%." );
                    return false;
                }
            }

            /*
            --=== water fairy usage
	        if player.Class1 == CLASS_PRIEST and self.Type == STYPE_SUMMON then
		        debug_skilluse("USINGPETFUNCTION");
		        checkfairy()
		        return false;
	        end

	        -- Enough mobs near AOE center?
	        if self.AOECenter and self.MobCount and self.MobCount > 1 then
		        if self.AOECenter == SAOE_PLAYER and self.Range and self.Range > 0 then
			        if player:countMobs(self.Range, settings.profile.options.COUNT_AGGRO_ONLY) < self.MobCount then
				        debug_skilluse("MOBCOUNTLOWNEARPLAYER");
				        return false
			        end
		        elseif self.AOECenter == SAOE_TARGET and self.AOERange and self.AOERange > 0 then
			        if target:findBestClickPoint(self.AOERange, self.Range, settings.profile.options.COUNT_AGGRO_ONLY) < self.MobCount then
				        debug_skilluse("MOBCOUNTLOWNEARTARGET");
				        return false
			        end
		        end
	        end
            */

            if ( this.Id == 490337 )//Rogue sneak attack, we need to be behind the target
            {
                if ( Helpers.AngleDifference( Player.Direction, target.Direction ) > 35 )
                {
                    DebugSkillUse( "Player needs to face the target." );
                    return false;
                }
            }

            if ( this.Id == 490317 ) //Rogue blind stab nees to be in front of target
            {
                if ( Helpers.AngleDifference( Player.Direction, target.Direction ) < 35 )
                {
                    DebugSkillUse( "Player needs to be behind the target." );
                    return false;
                }
            }

            return true;
        }

        private static Player Player
        {
            get
            {
                return Bot.Player;
            }
        }

        public bool CanUse1( bool onlyFriendly = false, Pawn target = null )
        {
            if ( this.HotKey == Key.VK_NONE )
                return false;

            if ( target != null )
            {
                this.Target = target;
            }

            if ( this.Target == null )
            {
                Bot.Player.UpdateTarget();
                this.Target = Bot.Player.Target;
            }

            if ( this.TargetType != SkillTargetType.Self && this.Target == null )
            {
                return false;
            }
            // only friendly skill types?
            if ( onlyFriendly )
            {
                if ( this.Type != SkillType.Heal &&
                    this.Type != SkillType.Buff &&
                    this.Type != SkillType.Summon &&
                    this.Type != SkillType.HoT )
                {
                    Logger.Warning( "Only friendly selected and this skill is not that type: " + this.Name );
                    return false;
                }
            }
            else
            {
                if ( this.Type != SkillType.Damage && this.Type != SkillType.DoT && this.Type != SkillType.Debuff )
                {
                    Logger.Warning( "Only damage selected and this skill is not that type: " + this.Name );
                    return false;
                }
            }

            // Still cooling down...
            if ( this.Cooldown > 0 )
            {
                //var usePrior = Bot.Player.Profile.GetOptionValue<int>( "SKILL_USE_PRIOR" );
                if ( RemainingCooldown >= 1 )
                {
                    Logger.Warning( "Skill is on cooldown: " + this.Name );
                    //Debug.WriteLine( "Skill is on CollDown {0} (remaining={1})", Name, remaining );
                    return false;
                }
            }
            //var cd = ( this.Cooldown * 1000f - this.RebuffCut * 1000f ) - Bot.Player.Profile.GetOptionValue<int>( "SKILL_USE_PRIOR" );
            //if ( ( this.LastCastTime - DateTime.Now ).TotalMilliseconds < cd ) // Cooldown is in sec
            //{
            //    Logger.Warning( "Skill is on cooldown: " + Name );
            //    Debug.WriteLine( "Skill is on CollDown {0} (cd={1})", Name, cd );
            //    return false;
            //}

            // You don't meet the maximum HP percent requirement
            if ( ( this.Type == SkillType.Heal || this.Type == SkillType.Buff || this.Type == SkillType.HoT ) && this.MaxHpPer != 0 )
            {
                if ( Bot.Player.MaxHP <= 0 )
                {
                    Bot.Player.MaxHP = 1;
                } // prevent division by zero
                if ( ( this.MaxHpPer < 0 ? -1 : 1 ) * ( Bot.Player.HP / Bot.Player.MaxHP * 100 ) > this.MaxHpPer )
                {
                    Logger.Warning( string.Format( "Player has: {0}% HP, skill requires: {1}% HP to be casted.", Bot.Player.HP / Bot.Player.MaxHP * 100, this.MaxHpPer ) );
                    return false;
                }
            }

            if ( ( this.MaxManaPer < 0 ? -1 : 1 ) * ( Bot.Player.Mana / Bot.Player.MaxMana * 100 ) > this.MaxManaPer )
            {
                //debug_skilluse("MAXMANAPER", (Bot.Player.Mana/Bot.Player.MaxMana*100), this.MaxManaPer);
                return false;
            }

            // You are ! above the minimum Mana Percent
            if ( ( Bot.Player.Mana / Bot.Player.MaxMana * 100 ) < this.MinManaPer )
            {
                //debug_skilluse("MINMANAPER", (Bot.Player.Mana/Bot.Player.MaxMana*100), this.MinManaPer);
                return false;
            }

            // Not enough rage/energy/concentration
            if ( Bot.Player.Rage < this.Rage || Bot.Player.Energy < this.Energy
                || Bot.Player.Focus < this.Concentration )
            {
                //debug_skilluse("NORAGEENERGYCONCEN");
                return false;
            }

            // This skill cannot be used in battle
            if ( !Bot.Player.Battling && ( this.InBattle ?? false ) )
            {
                //debug_skilluse("NOTINBATTLE", Bot.Player.Battling, Bot.Player.Fighting);
                return false;
            }

            if ( Bot.Player.Battling && !( this.InBattle ?? true ) )
            {
                return false;
            }

            // check if hp below our HP_LOW level
            if ( this.Type == SkillType.Heal || this.Type == SkillType.HoT )
            {
                var hpper = ( Bot.Player.HP / Bot.Player.MaxHP * 100 );

                if ( this.MaxHpPer != 100 )
                {
                    if ( hpper > this.MaxHpPer )
                    {
                        return false;
                    }
                }
                else
                {
                    // Inherit from settings' HP_LOW
                    if ( hpper > Bot.Player.Profile.GetOptionValue<int>( "HP_LOW" ) )
                    {
                        //debug_skilluse("HPLOW", hpper, "greater as setting", settings.profile.options.HP_LOW );
                        return false;
                    }
                }
            }


            // skill with maximum use per fight
            if ( this.MaxUse > 0 &&
                this.Used >= this.MaxUse )
            {
                //debug_skilluse("MAXUSE", this.used, this.maxuse);
                return false;
            }

            // Needs an enemy target
            if ( this.TargetType == SkillTargetType.Enemy )
            {
                if ( Bot.Player.Target == null )
                {
                    //debug_skilluse("TARGETNOENEMY");
                    return false;
                }
            }

            // DOTs require the enemy to have > X% hp
            if ( this.Type == SkillType.DoT )
            {
                if ( target == null )
                {
                    return false;
                }

                var hpper = ( target.HP / target.MaxHP ) * 100;
                if ( hpper < Bot.Player.Profile.GetOptionValue<int>( "DOT_PERCENT" ) )
                {
                    //debug_skilluse("DOTHPPER", hpper, settings.profile.options.DOT_PERCENT);
                    return false;
                }
            }

            // only if target HP % is below given level
            if ( target != null && ( ( this.TargetMaxHpPer < 0 ? -1 : 1 ) * ( target.HP / target.MaxHP * 100 ) ) > this.TargetMaxHpPer )
            {
                //debug_skilluse("TARGETHPPER", target.HP/target.MaxHP*100 );
                return false;
            }

            // only if target HP points is below given level
            if ( target != null && ( ( this.TargetMaxHp < 0 ? -1 : 1 ) * target.HP ) > this.TargetMaxHp )
            {
                //debug_skilluse("TARGEHP", target.HP );
                return false;
            }

            // Out of range
            if ( this.Range > 0 && Bot.Player.DistanceToTarget > this.Range &&
                this.TargetType != SkillTargetType.Self )
            {       // range check only if no selftarget skill
                //debug_skilluse("OUTOFRANGE", Bot.Player:distanceToTarget(), this.Range );
                return false;
            }

            // Too close
            if ( Bot.Player.DistanceToTarget < this.MinRange &&
                this.TargetType != SkillTargetType.Self )
            {       // range check only if no selftarget skill
                //debug_skilluse("MINRANGE", Bot.Player:distanceToTarget(), this.MinRange );
                return false;
            }

            // check pullonly skills
            if ( this.PullOnly &&
                !Bot.Player.RangedPull )
            {
                //debug_skilluse("PULLONLY");
                return false;
            }

            // Not enough mana
            if ( Bot.Player.Mana < Math.Ceiling( this.Mana + ( this.Level - 1 ) * this.ManaInc ) )
            {
                //debug_skilluse("NOTENOUGHMANA", Bot.Player.Mana, math.ceil(this.Mana + (this.Level-1)*this.ManaInc));
                return false;
            }

            // Already have a pet out
            if ( this.Type == SkillType.Summon && Bot.Player.PetPtr != 0 )
            {
                //debug_skilluse("PETALREAYDOUT");
                return false;
            }

            if ( this.Toggleable && this.Toggled )
            {
                //debug_skilluse("TOGGLED");
                return false;
            }

            if ( this.TargetType == SkillTargetType.Enemy && onlyFriendly )
            {
                return false;
            }

            if ( ( this.TargetType == SkillTargetType.Friendly || this.TargetType == SkillTargetType.Self || this.TargetType == SkillTargetType.Pet ) && !onlyFriendly )
            {
                return false;
            }

            // Check buff applied
            if ( ( this.Type == SkillType.Damage || this.Type == SkillType.DoT ) && target != null && target.Type == ObjectType.Monster && !string.IsNullOrEmpty( this.BuffName ) )
            {
                target.UpdateBuffs();
                var tiene = target.HasBuff( this.BuffName );
                if ( !tiene )
                {
                    return false;
                }
            }

            if ( ( this.Type == SkillType.Buff || this.Type == SkillType.HoT ) && ( this.TargetType == SkillTargetType.Self || this.TargetType == SkillTargetType.None || this.TargetType == SkillTargetType.Friendly ) )
            {
                Bot.Player.UpdateBuffs();
                var tiene = Bot.Player.HasBuff( "" + this.Id );
                if ( tiene )
                {
                    return false;
                }
            }

            if ( !string.IsNullOrEmpty( this.Consumable ) )
            {
                return false;
            }

            //if( this.ReqBuffName != "" ) {
            //    List<Buff> checktab;

            //    if( this.ReqBuffTarget == "Bot.Player" ) {
            //        if( this.ReqBuffType == "buff" ) {
            //            checktab = Bot.Player.Buffs;
            //        } else {
            //            checktab = Bot.Player.Debuffs;
            //        }
            //    } } else { if {( this.ReqBuffTarget == "target" ) {
            //        if( this.ReqBuffType == "buff" ) {
            //            checktab = target.Buffs;
            //        } else {
            //            checktab = target.Debuffs;
            //        }
            //    }

            //    if( this.ReqBuffCount > (checktab[this.ReqBuffName] || 0) ) {
            //        return false;
            //    }
            //}

            return true;
        }

        internal void ResetUse()
        {
#pragma warning disable 420
            Interlocked.Exchange( ref this.used, 0 );
#pragma warning restore 420
        }

        public string ID { get; set; }
        public string Name { get; set; }
        public int NeedLevel { get; set; }
        public int SkillTab { get; set; }
        public int SkillNum { get; set; }
        public int Mana { get; set; }
        public float ManaInc { get; set; }
        public int Range { get; set; }
        public float Cooldown { get; set; }
        public int Priority { get; set; }
        public float CastTime { get; set; }
        public SkillType Type { get; set; }
        public SkillTargetType TargetType { get; set; }
        public string Locale { get; set; }
        public int Level { get; set; }

        public Key HotKey { get; set; }
        public Key Modifier { get; set; }

        public DateTime LastCastTime { get; set; }
        public bool AutoUse { get; set; }
        public double AoERange { get; set; }
        public AoECenter AoECenter { get; set; }
        public bool PullOnly { get; set; }
        public int MaxUse { get; set; }
        public float RebuffCut { get; set; }
        public int Used
        {
            get { return this.used; }
            set { this.used = value; }
        }

        public int MinManaPer { get; set; }
        public int MaxManaPer { get; set; }
        public int TargetMinManaPer { get; set; }
        public int TargetMaxManaPer { get; set; }
        public int MaxHpPer { get; set; }
        public int TargetMaxHpPer { get; set; }
        public int MinMana { get; set; }
        public int MaxMana { get; set; }
        public int TargetMinMana { get; set; }
        public int TargetMaxMana { get; set; }
        public int MinHp { get; set; }
        public int MaxHp { get; set; }
        public int TargetMinHp { get; set; }
        public int TargetMaxHp { get; set; }

        public GameObject Target { get; set; }

        public bool Toggleable { get; set; }
        public bool Toggled { get; set; }

        public int Concentration { get; set; }

        public int Energy { get; set; }
        public int Rage { get; set; }
        public bool? InBattle { get; set; }

        public int MinRange { get; set; }

        public int BaseItemAddress { get; set; }

        public PowerType PowerType { get; set; }

        public int Address { get; set; }

        public int Focus { get; set; }

        public string Consumable { get; set; }

        public int ConsumableNumber { get; set; }

        public int Psi { get; set; }

        public int Id { get; set; }

        public int TPToLevel { get; set; }

        public int Aslevel { get; set; }

        public string BuffName { get; set; }

        public int ReqBuffCount { get; set; }

        public RequiredTargetType ReqBuffTarget { get; set; }

        public string ReqBuffName { get; set; }

        public int NoBuffCount { get; set; }

        public RequiredTargetType NoBuffTarget { get; set; }

        public string NoBuffName { get; set; }

        public bool ClickToCast { get; set; }

        public bool Blocking { get; set; }

        public bool GlobalCooldown { get; set; }

        public bool UsesPower
        {
            get { return this.Mana > 0 || this.Rage > 0 || this.Energy > 0 || this.Focus > 0; }
        }

        public double RemainingCooldown
        {
            get
            {
                if ( this.BaseItemAddress != 0 )
                {
                    var offset = Memory.ReadRepeat<int>( Bot.Instancia.ProcId, this.BaseItemAddress + Addresses.skillRemainingCooldown_offset );
                    if ( offset != 0 )
                        return Memory.ReadRepeat<int>( Bot.Instancia.ProcId, Addresses.staticCooldownsBase + ( offset + 1 ) * 4 ) / 10f;
                }
                return 0;
            }
        }

        public bool Available { get; set; }
        public int Nature { get; internal set; }

        public override string ToString()
        {
            return string.Format( "ID: {0}, Name: {1}, Level: {2}, AsLevel: {3}, Priority: {4}, AutoUse: {5}, Range: {6}, AoE range: {7}, MaxHpPer: {8}, TargetMaxHpPer: {9}, ReqBuffTarget: {10}, Mana: {11}, Rage: {12}, Energy: {13}, Focus: {14}", ID, Name, Level, Aslevel, Priority, AutoUse, Range, AoERange, MaxHpPer, TargetMaxHpPer, ReqBuffTarget, Mana, Rage, Energy, Focus );
        }
    }

    [StructLayout( LayoutKind.Explicit, Pack = 1, Size = 76 )]
    public class SkillBaseObject
    {
        [FieldOffset( 0 )]
        public int Id;

        [FieldOffset( Addresses.skillAsLevel_offset )]
        public byte skillAsLevel;

        [FieldOffset( Addresses.skillAttackFlag_offset )]
        public byte skillAttackFlag;

        [FieldOffset( Addresses.skillBuffFlag_offset )]
        public byte skillBuffFlag;

        [FieldOffset( Addresses.skillCastTime_offset )]
        public float skillCastTime;

        [FieldOffset( Addresses.skillClass_offset )]
        public int skillClass;

        [FieldOffset( Addresses.skillCooldown_offset )]
        public int skillCooldown;

        [FieldOffset( Addresses.skillRequiredEffectFlag_offset )]
        public byte skillRequiredEffectFlag;

        [FieldOffset( Addresses.skillRequiredEffect_offset )]
        public int skillRequiredEffect;

        [FieldOffset( Addresses.skillRequiredEffectsStart_offset )]
        public int skillRequiredEffectsStart;

        [FieldOffset( Addresses.skillSelfBuffFlag_offset )]
        public byte skillSelfBuffFlag;

        [FieldOffset( Addresses.skillTPToLevel_offset )]
        public int skillTPToLevel;

        [FieldOffset( Addresses.skillTargetType_offset )]
        public byte skillTargetType;

        [FieldOffset( Addresses.skillTypeFlag1_offset )]
        public byte skillTypeFlag1;

        [FieldOffset( Addresses.skillTypeFlag2_offset )]
        public byte skillTypeFlag2;

        [FieldOffset( Addresses.skillTypeFlag3_offset )]
        public byte skillTypeFlag3;

        [FieldOffset( Addresses.skillTypeFlag4_offset )]
        public byte skillTypeFlag4;

        [FieldOffset( Addresses.skillTypeFlag5_offset )]
        public byte skillTypeFlag5;

        [FieldOffset( Addresses.skillTypeFlag6_offset )]
        public byte skillTypeFlag6;

        [FieldOffset( Addresses.skillTypeFlag7_offset )]
        public byte skillTypeFlag7;

        [FieldOffset( Addresses.skillTypeFlag8_offset )]
        public byte skillTypeFlag8;

        [FieldOffset( Addresses.skillTypeFlag9_offset )]
        public byte skillTypeFlag9;

        [FieldOffset( Addresses.skillUsesBase_offset )]
        public byte skillUsesBase;

        [FieldOffset( Addresses.skillLevel_offset )]
        public int skillLevel;

        [FieldOffset( Addresses.skillMaxLevel_offset )]
        public int skillMaxLevel;

        [FieldOffset( Addresses.skillPassiveFlag_offset )]
        public byte skillPassiveFlag;

        [FieldOffset( Addresses.skillRangeAoE_offset )]
        public int skillRangeAoE;

        [FieldOffset( Addresses.skillRange_offset )]
        public int skillRange;
    }

    [StructLayout( LayoutKind.Explicit, Pack = 1, Size = 76 )]
    public class TabSkill
    {
        [FieldOffset( 0 )]
        public int Id;

        [FieldOffset( 4 )]
        public int _04;

        [FieldOffset( Addresses.skillTPToLevel_offset )]
        public int skillTPToLevel;

        [FieldOffset( Addresses.skillLevel_offset )]
        public int skillLevel;

        [FieldOffset( 16 )]
        public int _16;

        [FieldOffset( 20 )]
        public int _20;

        [FieldOffset( Addresses.skillAsLevel_offset )]
        public byte skillAsLevel;

        [FieldOffset( 28 )]
        public int _28;

        [FieldOffset( 32 )]
        public int _32;

        [FieldOffset( 36 )]
        public int _36;

        [FieldOffset( 40 )]
        public int _40;

        [FieldOffset( 44 )]
        public int _44;

        [FieldOffset( 48 )]
        public int _48;

        [FieldOffset( 52 )]
        public int _52;

        [FieldOffset( 56 )]
        public int _56;

        [FieldOffset( 60 )]
        public int _60;

        [FieldOffset( 64 )]
        public int _64;

        [FieldOffset( 68 )]
        public int _68;

        [FieldOffset( 72 )]
        public int _72;
    }
}
